我时不时听到“使用 bcrypt 在 PHP 中存储密码,bcrypt 规则”的建议。
但什么是bcrypt?PHP 不提供任何此类功能,Wikipedia 喋喋不休地谈论文件加密实用程序,而 Web 搜索仅揭示了Blowfish在不同语言中的一些实现。现在 Blowfish 也可以通过 PHP 在 PHP 中使用mcrypt,但是这对存储密码有什么帮助呢?Blowfish 是一种通用密码,它有两种工作方式。如果可以加密,就可以解密。密码需要一种单向散列函数。
bcrypt
mcrypt
解释是什么?
bcrypt是一种散列算法,可通过硬件扩展(通过可配置的轮数)。它的缓慢和多轮确保攻击者必须部署大量资金和硬件才能破解您的密码。添加到每个密码的盐(bcrypt需要盐),您可以确定如果没有大量资金或硬件,攻击几乎是不可行的。
bcrypt使用 Eksblowfish 算法对密码进行哈希处理。 虽然Eksblowfish 和 Blowfish 的加密阶段完全相同,但 Eksblowfish 的密钥调度阶段确保任何后续状态都依赖于盐和密钥(用户密码),并且在两者都不知情的情况下无法预先计算任何状态。 由于这个关键区别,bcrypt是一种单向散列算法。 在不知道盐、轮数和密钥 (密码)的情况下,您无法检索纯文本密码。[来源]
密码散列函数现已直接内置于 PHP >= 5.5中。您现在可以password_hash()用来创建bcrypt任何密码的哈希:
password_hash()
<?php // Usage 1: echo password_hash('rasmuslerdorf', PASSWORD_DEFAULT)."\n"; // $2y$10$xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx // For example: // $2y$10$.vGA1O9wmRjrwAVXD98HNOgsNpDczlqm3Jq7KnEd1rVAGv3Fykk1a // Usage 2: $options = [ 'cost' => 11 ]; echo password_hash('rasmuslerdorf', PASSWORD_BCRYPT, $options)."\n"; // $2y$11$6DP.V0nO7YI3iSki4qog6OQI5eiO6Jnjsqg7vdnb.JgGIsxniOn4C
要根据现有哈希验证用户提供的密码,您可以password_verify()这样使用:
password_verify()
<?php // See the password_hash() example to see where this came from. $hash = '$2y$07$BCryptRequires22Chrcte/VlQH0piJtjXl.0t1XkA8pw9dMXTpOq'; if (password_verify('rasmuslerdorf', $hash)) { echo 'Password is valid!'; } else { echo 'Invalid password.'; }
GitHub 上有一个兼容库,它是根据上述最初用 C 编写的函数的源代码创建的,它提供了相同的功能。一旦安装了兼容性库,用法与上面相同(如果您仍在 5.3.x 分支上,则减去速记数组表示法)。
您可以使用crypt()函数生成输入字符串的 bcrypt 哈希。此类可以自动生成盐并根据输入验证现有哈希。 如果您使用的 PHP 版本高于或等于 5.3.7,强烈建议您使用内置函数或兼容库 。此替代方案仅出于历史目的而提供。
crypt()
class Bcrypt{ private $rounds; public function __construct($rounds = 12) { if (CRYPT_BLOWFISH != 1) { throw new Exception("bcrypt not supported in this installation. See http://php.net/crypt"); } $this->rounds = $rounds; } public function hash($input){ $hash = crypt($input, $this->getSalt()); if (strlen($hash) > 13) return $hash; return false; } public function verify($input, $existingHash){ $hash = crypt($input, $existingHash); return $hash === $existingHash; } private function getSalt(){ $salt = sprintf('$2a$%02d$', $this->rounds); $bytes = $this->getRandomBytes(16); $salt .= $this->encodeBytes($bytes); return $salt; } private $randomState; private function getRandomBytes($count){ $bytes = ''; if (function_exists('openssl_random_pseudo_bytes') && (strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN')) { // OpenSSL is slow on Windows $bytes = openssl_random_pseudo_bytes($count); } if ($bytes === '' && is_readable('/dev/urandom') && ($hRand = @fopen('/dev/urandom', 'rb')) !== FALSE) { $bytes = fread($hRand, $count); fclose($hRand); } if (strlen($bytes) < $count) { $bytes = ''; if ($this->randomState === null) { $this->randomState = microtime(); if (function_exists('getmypid')) { $this->randomState .= getmypid(); } } for ($i = 0; $i < $count; $i += 16) { $this->randomState = md5(microtime() . $this->randomState); if (PHP_VERSION >= '5') { $bytes .= md5($this->randomState, true); } else { $bytes .= pack('H*', md5($this->randomState)); } } $bytes = substr($bytes, 0, $count); } return $bytes; } private function encodeBytes($input){ // The following is code from the PHP Password Hashing Framework $itoa64 = './ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; $output = ''; $i = 0; do { $c1 = ord($input[$i++]); $output .= $itoa64[$c1 >> 2]; $c1 = ($c1 & 0x03) << 4; if ($i >= 16) { $output .= $itoa64[$c1]; break; } $c2 = ord($input[$i++]); $c1 |= $c2 >> 4; $output .= $itoa64[$c1]; $c1 = ($c2 & 0x0f) << 2; $c2 = ord($input[$i++]); $c1 |= $c2 >> 6; $output .= $itoa64[$c1]; $output .= $itoa64[$c2 & 0x3f]; } while (true); return $output; } }
您可以像这样使用此代码:
$bcrypt = new Bcrypt(15); $hash = $bcrypt->hash('password'); $isGood = $bcrypt->verify('password', $hash);
或者,您也可以使用Portable PHP Hashing Framework。